package com.itu.modernai;

import cz.cuni.amis.pogamut.ut2004.bot.command.CompleteBotCommandsWrapper;
import java.io.*;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

public class GeneManager {

  
    public enum SELECTION{ELITISM, ROULETTEWHEEL, TOURNAMENT};
    static enum CROSSOVER{SINGLE_POINT, TWO_POINT, MULTI_POINT};
    
    SELECTION selectionMode = SELECTION.ELITISM;
    CROSSOVER crossoverMode = CROSSOVER.TWO_POINT;
    
    static int NextGene;
    
    private boolean outputFitness = true;
    
    static int GENERATION = 1;
    static int MAX_GENERATIONS = 10;
    static int POPULATION_SIZE = 1;
    //static float MUTATION_RATE = 0.07f;
    static float MUTATION_RATE1 = 0.143f;
    static float CROSSOVER_RATE = 0.7f;
    
    static float m_totalFitnessScore = 0;
    static List<Gene> m_population;
    
    private static GeneManager instance = null;
    
    Random _rand = new Random(System.currentTimeMillis());
    
    public static GeneManager getInstance(){
        if(instance == null)
            instance = new GeneManager(true);
        return instance;
    }
    
    private GeneManager(boolean randomGeneration){
        NextGene = -1;
        if(randomGeneration && m_population == null)
             m_population = generateRandomPopulation();  
        if(!randomGeneration && m_population == null)
            m_population = new ArrayList<Gene>();
    }
    
    private List<Gene> generateRandomPopulation() {
        List<Gene> nPop = new ArrayList<Gene>();
        for(int i=0; i<POPULATION_SIZE; i++)
            nPop.add(new Gene(_rand));
        

        
        return nPop;
    }
    
    
    
    /**
     * 1. sort genes in population.
     * 2. remove bad half.
     * 3. reproduce from the best genes.
     * 4. mutate if mutation rate is met.
     */
    public void evaluateGeneration(){
        m_totalFitnessScore = 0;
        for(int i=0; i<POPULATION_SIZE; i++)
        {
              m_totalFitnessScore += getGene(i).getFitness();
              //getGene(i).updateFitness();
        }
          
    }
    
    void produceNextGeneration(CompleteBotCommandsWrapper chat){
       /* if(GENERATION == MAX_GENERATIONS){
            
        }*/ 
        
        //det hér er også galt. skal vidst være i en anden funktion.
        Gene[] newcomers = new Gene[POPULATION_SIZE];
        int numberOfChildren = 0;
        
        //ELITISM
        if(selectionMode == SELECTION.ELITISM)
            elitism(chat);
        else {
        
            while(numberOfChildren < POPULATION_SIZE){
                Gene[] parents = new Gene[2];
                if(selectionMode == SELECTION.ROULETTEWHEEL){
                    parents[0] = rouletteWheelSelection();
                    parents[1] = rouletteWheelSelection();
                } else if(selectionMode == SELECTION.TOURNAMENT){
                    parents[0] = tournamentSelection(new Random().nextInt(POPULATION_SIZE-1));
                    parents[1] = tournamentSelection(new Random().nextInt(POPULATION_SIZE-1));
                }
                Gene[] children = reproduce(parents[0], parents[1]);
                
                children[0].mutate();
                children[1].mutate();
            
                newcomers[numberOfChildren] = children[0];
                newcomers[numberOfChildren+1] = children[1];
            
                numberOfChildren+=2;
            }
        
            for(int i=0; i<POPULATION_SIZE; i++)
                getGene(i).setChromosome(newcomers[i].getChromosome());
            
        }
        GENERATION++;
        try{WriteToText("runs/g15_" + Integer.toString(GENERATION) + ".txt", chat);}
        catch(Exception e){chat.getCommunication().sendGlobalTextMessage("Failed to write .txt file: " + e.getLocalizedMessage());}
            
    }
    
    Gene getGene(int index){ return m_population.get(index);}
    int getSize(){ return m_population.size();}
    
    Gene[] reproduce(Gene dad, Gene mom){        
        return crossOver(mom, dad);
    }
    
    Gene getNextGene(CompleteBotCommandsWrapper chat)
    {
        if(NextGene == POPULATION_SIZE-1){
            evaluateGeneration();
            WriteOldPopulationToList();
            produceNextGeneration(chat);
            NextGene = -1;
        }
        
        NextGene++;
        int genesLeft = POPULATION_SIZE - 1 - NextGene;
        chat.getCommunication().sendGlobalTextMessage("Adding new gene to bot! Number of genes left in current generation: " + genesLeft);
        return m_population.get(NextGene);
    }
      private void WriteOldPopulationToList() 
      {
          Collections.sort(m_population);
        for (Gene gene : m_population) 
        {
            try {
                StatsWriter.getInstance().WriteGene(gene.toString()+"," + gene.getName()+","+gene.getFitness(), "EvaluatedGeneration_"+GENERATION);
            } catch (IOException ex) {
                Logger.getLogger(GeneManager.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    void WriteToText(String fileName, CompleteBotCommandsWrapper chat) throws IOException
    {
        Collections.sort(m_population);
        BufferedWriter bw = new BufferedWriter(new FileWriter(new File(
                        fileName), true));
        for (Gene gene : m_population) {
            //bw.write(gene.toString());
            bw.write(gene.toString());
            bw.newLine();
        }
        bw.close();
        chat.getCommunication().sendGlobalTextMessage("Chromosomes of current generation was succesfully written to " + fileName);
    }
    
    static GeneManager FromTextFile(String fileName) throws FileNotFoundException, IOException
    {
        if(instance == null){
            instance = new GeneManager(false);
            BufferedReader br = new BufferedReader(new FileReader(fileName));
            String line;
            while ((line = br.readLine()) != null && !"".equals(line)) {
                instance.addToPopulationFromTextLine(line);
            }
            br.close();
            
            
        }
        return instance;
    }
    
    void addToPopulationFromTextLine(String line)
    {
        String delim = "[,]";
        String[] stringChromozome = line.split(delim);
        float[] chromo = new float[stringChromozome.length];
        for (int i = 0; i < stringChromozome.length; i++) {
            chromo[i] = Float.parseFloat(stringChromozome[i]);
        }
        if(m_population == null)
            m_population = new ArrayList<Gene>();
        m_population.add(new Gene(chromo));
    }
    
    //multipoint cross (currently 1 and 2 works)
    Gene[] crossOver(Gene mom, Gene dad){
        Gene[] babies = new Gene[2];
        babies[0] = new Gene();
        babies[1] = new Gene();
        
        if( (new Random().nextFloat() > CROSSOVER_RATE) || (mom == dad)){
            babies[0] = dad;
            babies[1] = mom;

            return babies;
        }
        
        
        
        switch(crossoverMode){
            case SINGLE_POINT:
                int randomPick = new Random().nextInt(Gene.CHROMOSOME_SIZE-1);
        
                for(int i=0; i<Gene.CHROMOSOME_SIZE; i++){
                    if(i < randomPick){
                        babies[0].setChromosomeElement(i, mom.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, dad.getChromosomeElement(i));
                    } else {
                        babies[0].setChromosomeElement(i, dad.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, mom.getChromosomeElement(i));
                    }
                }
                break;
                
            case TWO_POINT:
                int randomPick1 = new Random().nextInt(Gene.CHROMOSOME_SIZE-2);
                //pick a random number between last pick and length of chromosome
                int randomPick2 = new Random().nextInt((Gene.CHROMOSOME_SIZE-1) - (randomPick1 +1)) + randomPick1;
                
                for(int i=0; i<Gene.CHROMOSOME_SIZE; i++){
                    if(i > randomPick1 && i < randomPick2){
                        babies[0].setChromosomeElement(i, mom.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, dad.getChromosomeElement(i));
                    } else {
                        babies[0].setChromosomeElement(i, dad.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, mom.getChromosomeElement(i));
                    }
                }
                break;
                
            case MULTI_POINT:
                for(int i=0; i<Gene.CHROMOSOME_SIZE; i++){
                    if(new Random().nextFloat() < CROSSOVER_RATE){
                        babies[0].setChromosomeElement(i, mom.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, dad.getChromosomeElement(i));
                    } else {
                        babies[0].setChromosomeElement(i, dad.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, mom.getChromosomeElement(i));
                    }
                }
                break;
        }
        
        return babies;
    }
    
    Gene rouletteWheelSelection(){
        Collections.sort(m_population);
        double slice = new Random().nextFloat() * m_totalFitnessScore;
        double fitnessSum = 0;
        int selection = 0;
        
        for(int i=0; i<POPULATION_SIZE; i++){
            fitnessSum += m_population.get(i).getFitness();
            
            if(fitnessSum > slice)
                return m_population.get(i);
        }
        
        return m_population.get(selection);
    }
    
    Gene tournamentSelection(int count){
        double bestFitness = 0;
        int selection = 0;
        
        for(int i=0; i<count; i++){
            int randomSelection = new Random().nextInt(POPULATION_SIZE-1);
            
            if(getGene(randomSelection).getFitness() > bestFitness){
                selection = randomSelection;
                bestFitness = getGene(randomSelection).getFitness();
            }
        }
        
        return getGene(selection);
    }
    
    void elitism(CompleteBotCommandsWrapper chat){
        Collections.sort(m_population);
        int numberOfChildren = 0;
        Gene[] newcomers = new Gene[POPULATION_SIZE/2];
        
        chat.getCommunication().sendGlobalTextMessage("GeneManager is now performing ELITISM! current Generation: " + GENERATION);
        
        //destroy the bad seeds:
        for(int i=0; i<POPULATION_SIZE/2; i++){
            chat.getCommunication().sendGlobalTextMessage("Killing gene with fitness: " + m_population.get(0).getFitness());
            m_population.remove(0);
        }
        
       /* String top3Fitness = Float.toString(m_population.get(m_population.size()-1).getFitness()) + ", ";
        top3Fitness += Float.toString(m_population.get(m_population.size()-2).getFitness()) + ", ";
        top3Fitness += Float.toString(m_population.get(m_population.size()-3).getFitness());
        chat.getCommunication().sendGlobalTextMessage("Top 3 genes have fitness: " + top3Fitness);*/
        
        //create new children from the good half!
        while(numberOfChildren < (POPULATION_SIZE / 2)-1){
            
            Gene[] parents = {m_population.get(numberOfChildren), m_population.get(numberOfChildren+1)}; 
            Gene[] children = reproduce(parents[0], parents[1]);
            
            children[0].mutate();
            children[1].mutate();
            
            newcomers[numberOfChildren] = children[0];
            newcomers[numberOfChildren+1] = children[1];
            
            numberOfChildren+=2;
        }
        
        for(int i=0; i<POPULATION_SIZE/2; i++)
            m_population.add(newcomers[i]);
    }
    
  
}
